package com.nulldev.util.web.YouTubeExtractor;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.RandomUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.URLUtils.Entry;
import com.nulldev.util.web.YouTubeExtractor.extractors.util.YTIAPIClientType;
import com.nulldev.util.web.YouTubeExtractor.util.FakeVisitorID;
import com.nulldev.util.web.YouTubeExtractor.util.YTEClientDateGenerator;

public class YouTubeExtractorConstants {

	public static final int SIGNATURE_TIMESTAMP_FALLBACK = 18948;
	public static final String CLIENT_VERSION_CAN_EXP_SUFFIX = "-canary_experiment";

	public static final boolean ENABLE_VARIANTS_CHECKSUM = Arguments.hasArgument("--YouTubeExtractor.enableVariantsChecksum");
	public static final boolean ENABLE_CANARY_EXPERIMENT = Arguments.hasArgument("--YouTubeExtractor.canary");

	public static String CLIENT_VERSION = getFor("clientVersion");
	public static String PAGE_CL = getFor("pageCl");
	public static String PAGE_LABEL = getFor("pageLabel");
	public static String PAGE_STS = getFor("pageSts");
	public static String UTC_OFFSET = getFor("utcOffset");
	public static String SIGNATURE_TIMESTAMP = getFor("signatureTimestamp");
	public static String VISITOR_DATA = getFor("visitorData");
	public static String VARIANTS_CHECKSUM = ENABLE_VARIANTS_CHECKSUM ? "8d80ca98a2219d0848ef9b4d969bf4be" : null;
	public static String API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; // hue hue hue

	public static volatile String XSRF_TOKEN = null;
	public static final boolean USE_AUTO_GENERATE_FAKES = Arguments.hasArgument("--YouTubeExtractor.fakeInitialConstants");

	private static final SimpleDateFormat format = new SimpleDateFormat("YYYYMMDD");

	private static String getFor(final String id) {
		final Date d = new Date();
		final Calendar c = Calendar.getInstance();
		c.setTime(d);
		switch (id) {
			case "clientVersion": {
				if (USE_AUTO_GENERATE_FAKES) {
					return YTEClientDateGenerator.makeDate(c, YTIAPIClientType.WEB);
				} else {
					return "2.20230602.01.00";
				}
			}
			case "pageCl":
				return "270169268";
			case "pageLabel": {
				if (USE_AUTO_GENERATE_FAKES) {
					return "youtube.ytfe.desktop_" + format.format(d) + "_" + RandomUtil.randInt(0, 6) + "_RC" + RandomUtil.randInt(0, 6);
				} else {
					return "youtube.ytfe.desktop_20230602_5_RC1";
				}
			}
			case "pageSts":
				return "3854";
			case "utcOffset":
				return "120";
			case "signatureTimestamp":
				return String.valueOf(SIGNATURE_TIMESTAMP_FALLBACK);
			case "visitorData": {
				if (USE_AUTO_GENERATE_FAKES) {
					return FakeVisitorID.make();
				} else {
					// This is what nTube used to expose.
					return "CgtRN09EMGVtU19Gbyip6fGHBg%3D%3D";
				}
			}
			default:
				return null;
		}
	}

	public static enum YTGenericEntryVersion {
		VERSION_1,
		VERSION_2
	}

	public static Entry[] makeGenericEntries(final YTGenericEntryVersion ver) {
		switch (ver) {
			case VERSION_2:
				return new Entry[]
					{ new Entry("X-YouTube-Client-Name", "1"), new Entry("X-YouTube-Client-Version", CLIENT_VERSION), };
			case VERSION_1:
			default:
				return new Entry[]
					{ new Entry("X-YouTube-Client-Name", "1"), new Entry("X-YouTube-Client-Version", CLIENT_VERSION), new Entry("X-YouTube-Page-CL", PAGE_CL),
							new Entry("X-YouTube-Page-Label", PAGE_LABEL), new Entry("X-YouTube-STS", PAGE_STS), new Entry("X-YouTube-Utc-Offset", UTC_OFFSET),
							new Entry("X-YouTube-Variants-Checksum", VARIANTS_CHECKSUM), };
		}
	}

	private static final Logger log = LoggerFactory.getLoggerD(Arguments.hasArgument("--debug", "-debug", "--trace", "--YouTubeExtractorConstants.debug"));

	public static YouTubeFrontendMeta create(final String html) {
		return new YouTubeFrontendMeta(html);
	}

	public static void auto(final String html) {
		final YouTubeFrontendMeta mm = create(html);
		if (mm.clientVersion != null) {
			try {
				log.debug("clientVersion: " + CLIENT_VERSION + " -> " + ("2" + mm.clientVersion.substring(1)));
				CLIENT_VERSION = ("2" + mm.clientVersion.substring(1));

				if (ENABLE_CANARY_EXPERIMENT) {
					CLIENT_VERSION += CLIENT_VERSION_CAN_EXP_SUFFIX;
				}
			} catch (Exception e) {
				log.error("Failed to parse: CLIENT_VERSION: " + mm.clientVersion);
				throw new RuntimeException(e);
			}
		} else {
			log.warn("auto(string) -> CLIENT_VERSION would be null!");
		}
		if (mm.pageCl != null) {
			log.debug("pageCl: " + PAGE_CL + " -> " + mm.pageCl);
			PAGE_CL = mm.pageCl;
		} else {
			log.warn("auto(string) -> PAGE_CL would be null!");
		}
		if (mm.pageLabel != null) {
			log.debug("pageLabel: " + PAGE_LABEL + " -> " + mm.pageLabel);
			PAGE_LABEL = mm.pageLabel;
		} else {
			log.warn("auto(string) -> PAGE_LABEL would be null!");
		}
		if (mm.pageSts != null) {
			log.debug("pageSts: " + PAGE_STS + " -> " + mm.pageSts);
			PAGE_STS = mm.pageSts;
		} else {
			log.warn("auto(string) -> PAGE_STS would be null!");
		}
		if (mm.utcOffset != null) {
			UTC_OFFSET = mm.utcOffset;
		} else {
			log.warn("auto(string) -> UTC_OFFSET would be null!");
		}
		if (mm.variantsChecksum != null) {
			log.debug("variantsChecksum: " + VARIANTS_CHECKSUM + " -> " + mm.variantsChecksum);
			VARIANTS_CHECKSUM = mm.variantsChecksum;
		} else if (ENABLE_VARIANTS_CHECKSUM) {
			log.warn("auto(string) -> VARIANTS_CHECKSUM would be null!");
		} else {
			log.info("auto(string) -> Ignoring null VARIANTS_CHECKSUM.");
		}
		if (mm.xsrfToken != null) {
			XSRF_TOKEN = mm.xsrfToken;
		} else {
			log.warn("auto(string) -> XSRF_TOKEN would be null!");
		}
		if (mm.apiKey != null) {
			log.debug("apiKey: " + API_KEY + " -> " + mm.apiKey);
			API_KEY = mm.apiKey;
		} else {
			log.warn("auto(string) -> API_KEY would be null!");
		}
		if (mm.signatureTimestamp != null) {
			log.debug("signatureTimestamp: " + SIGNATURE_TIMESTAMP + " -> " + mm.signatureTimestamp);
			SIGNATURE_TIMESTAMP = mm.signatureTimestamp;
		} else {
			log.warn("auto(string) -> SIGNATURE_TIMESTAMP would be null!");
		}
		if (mm.visitorData != null) {
			log.debug("visitorData: " + VISITOR_DATA + " -> " + mm.visitorData);
			VISITOR_DATA = mm.visitorData;
		} else {
			log.warn("auto(string) -> VISITOR_DATA would be null!");
		}
		log.info("Updated extractor constants.");
	}

	public static class YouTubeFrontendMeta {
		private String clientVersion;
		private String pageCl;
		private String pageLabel;
		private String pageSts;
		private String utcOffset;
		private String variantsChecksum;
		private String xsrfToken;
		private String apiKey;
		private String signatureTimestamp;
		private String visitorData;

		public YouTubeFrontendMeta(final String cv, final String pcl, final String plbl, final String psts, final String utcoff, final String vchck,
				final String xsrfToken, final String apiKey, final String signatureTimestamp, final String visitorData) {
			this.clientVersion = cv;
			this.pageCl = pcl;
			this.pageLabel = plbl;
			this.pageSts = psts;
			this.utcOffset = utcoff;
			this.variantsChecksum = vchck;
			this.xsrfToken = xsrfToken;
			this.apiKey = apiKey;
			this.signatureTimestamp = signatureTimestamp;
			this.visitorData = visitorData;
		}

		public YouTubeFrontendMeta(final String htmlPage) {
			this.pageLabel = StringsUtil.substringBetweenO(htmlPage, "\"PAGE_BUILD_LABEL\":\"", "\",\"");
			if (this.pageLabel == null) {
				this.pageLabel = StringsUtil.substringBetweenO(htmlPage, "'PAGE_BUILD_LABEL': \"", "\",'");
			}
			if (this.pageLabel == null) {
				this.pageLabel = find_value(htmlPage, "PAGE_BUILD_LABEL");
			}
			if (this.pageLabel == null) {
				this.pageLabel = find_value_alt(htmlPage, "PAGE_BUILD_LABEL");
			}
			if (this.pageLabel != null && this.pageLabel.contains("\"});")) {
				this.pageLabel = this.pageLabel.substring(0, this.pageLabel.indexOf("\"});"));
				log.warn("Fixed pageLabel.");
			}
			if (this.pageLabel != null && this.pageLabel.endsWith(",")) {
				this.pageLabel = this.pageLabel.substring(0, this.pageLabel.length() - 2);
				log.warn("Fixed pageLabel.");
			}
			this.pageCl = StringsUtil.substringBetweenO(htmlPage, "\"PAGE_CL\":", ",");
			if (this.pageCl == null) {
				this.pageCl = StringsUtil.substringBetweenO(htmlPage, ",'PAGE_CL': ", ",'");
				if (this.pageCl != null && this.pageCl.contains("style="))
					this.pageCl = null;
			}
			if (this.pageCl == null) {
				this.pageCl = find_value(htmlPage, "PAGE_CL");
				if (this.pageCl != null && this.pageCl.contains("style="))
					this.pageCl = null;
			}
			if (this.pageCl == null) {
				this.pageCl = find_value_alt(htmlPage, "PAGE_CL");
				if (this.pageCl != null && this.pageCl.contains("style="))
					this.pageCl = null;
			}
			if (this.pageCl != null && this.pageCl.contains("\"});")) {
				this.pageCl = this.pageCl.substring(0, this.pageCl.indexOf("\"});"));
				log.warn("Fixed pageCl.");
			}
			if (this.pageCl != null && this.pageCl.contains("});")) {
				this.pageCl = this.pageCl.substring(0, this.pageCl.indexOf("});"));
				log.warn("Fixed pageCl.");
			}
			this.clientVersion = StringsUtil.substringBetweenO(htmlPage, "\"INNERTUBE_CONTEXT_CLIENT_VERSION\":\"", "\",\"");
			if (this.clientVersion == null) {
				this.clientVersion = StringsUtil.substringBetweenO(htmlPage, ",\"innertube_context_client_version\":\"", "\",\"");
				if (this.clientVersion == null) {
					this.clientVersion = find_value(htmlPage, "INNERTUBE_CONTEXT_CLIENT_VERSION");
				}
				if (this.clientVersion == null) {
					this.clientVersion = find_value_alt(htmlPage, "INNERTUBE_CONTEXT_CLIENT_VERSION");
				}
				if (this.clientVersion == null) {
					this.clientVersion = find_value(htmlPage, "innertube_context_client_version");
				}
				if (this.clientVersion == null) {
					this.clientVersion = find_value_alt(htmlPage, "innertube_context_client_version");
				}
				if (this.clientVersion != null && this.clientVersion.length() > 50) {
					this.clientVersion = StringsUtil.substringBetweenO(htmlPage, ",\"innertube_context_client_version\":\"", "\"},\"");
				}
			}
			if (this.clientVersion != null && this.clientVersion.contains("\"}};")) {
				this.clientVersion = this.clientVersion.substring(0, this.clientVersion.indexOf("\"});"));
				log.warn("Fixed clientVersion.");
			}
			if (this.clientVersion != null && this.clientVersion.contains("\"});")) {
				this.clientVersion = this.clientVersion.substring(0, this.clientVersion.indexOf("\"});"));
				log.warn("Fixed clientVersion.");
			}
			this.utcOffset = "120"; // TODO: Automatically use local data.
			this.pageSts = StringsUtil.substringBetweenO(htmlPage, "\"ROOT_VE_TYPE\":", ",");
			// FIXME: May return invalid values.
			if (this.pageSts == null) {
				this.pageSts = StringsUtil.substringBetweenO(htmlPage, "ytcfg.set(\"ROOT_VE_TYPE\", ", ");");
			}
			if (this.pageSts != null && this.pageSts.contains("\"});")) {
				this.pageSts = this.pageSts.substring(0, this.pageSts.indexOf("\"});"));
				log.warn("Fixed pageSts.");
			}
			if (this.pageSts != null && this.pageSts.contains("});")) {
				this.pageSts = this.pageSts.substring(0, this.pageSts.indexOf("});"));
				log.warn("Fixed pageSts.");
			}
			if (this.pageSts != null) {
				if (this.pageSts.startsWith("\""))
					this.pageSts = this.pageSts.substring(1);
				if (this.pageSts.endsWith("\""))
					this.pageSts = this.pageSts.substring(0, this.pageSts.length() - 1);
			}
			if (ENABLE_VARIANTS_CHECKSUM) {
				this.variantsChecksum = StringsUtil.substringBetweenO(htmlPage, "\"VARIANTS_CHECKSUM\":\"", "\",");
				if (this.variantsChecksum != null && (this.variantsChecksum.contains("style=") || this.variantsChecksum.contains(" "))) {
					this.variantsChecksum = null;
				}
				if (this.variantsChecksum == null) {
					this.variantsChecksum = StringsUtil.substringBetweenO(htmlPage, ",\"VARIANTS_CHECKSUM\":\"", "\",\"");
					if (this.variantsChecksum == null) {
						this.variantsChecksum = StringsUtil.substringBetweenO(htmlPage, ",'VARIANTS_CHECKSUM': \"", "\",'");
					}
					if (this.variantsChecksum == null) {
						this.variantsChecksum = find_value(htmlPage, "VARIANTS_CHECKSUM");
					}
					if (this.variantsChecksum == null) {
						this.variantsChecksum = find_value_alt(htmlPage, "VARIANTS_CHECKSUM");
					}
				}
				if (this.variantsChecksum != null && this.variantsChecksum.contains("\"});")) {
					this.variantsChecksum = this.variantsChecksum.substring(0, this.variantsChecksum.indexOf("\"});"));
					log.warn("Fixed variantsChecksum.");
				}
			}
			this.xsrfToken = StringsUtil.substringBetweenO(htmlPage, "\"XSRF_TOKEN\":\"", "\",");
			if (this.xsrfToken == null) {
				this.xsrfToken = StringsUtil.substringBetweenO(htmlPage, "'XSRF_TOKEN': \"", "\",");
			}
			if (this.xsrfToken == null) {
				this.xsrfToken = find_value(htmlPage, "XSRF_TOKEN");
			}
			if (this.xsrfToken == null) {
				this.xsrfToken = find_value_alt(htmlPage, "XSRF_TOKEN");
			}
			this.apiKey = StringsUtil.substringBetweenO(htmlPage, "\"INNERTUBE_API_KEY\":\"", "\",\"");
			if (this.apiKey == null) {
				this.apiKey = StringsUtil.substringBetweenO(htmlPage, "'INNERTUBE_API_KEY': \"", "\",'");
				if (this.apiKey == null) {
					this.apiKey = StringsUtil.substringBetweenO(htmlPage, ",INNERTUBE_API_KEY: \"", "\"");
				}
				if (this.apiKey == null) {
					this.apiKey = find_value(htmlPage, "INNERTUBE_API_KEY");
				}
				if (this.apiKey == null) {
					this.apiKey = find_value_alt(htmlPage, "INNERTUBE_API_KEY");
				}
			}
			if (this.apiKey != null && this.apiKey.contains("\"});")) {
				this.apiKey = this.apiKey.substring(0, this.apiKey.indexOf("\"});"));
				log.warn("Fixed apiKey.");
			}
			this.visitorData = StringsUtil.substringBetweenO(htmlPage, "\"visitorData\":\"", "\",\"");
			this.signatureTimestamp = StringsUtil.cleanInvalidCharacters(StringsUtil.substringBetweenO(htmlPage, "\"STS\":", ",\""));
		}

		public String getClientVersion() {
			return this.clientVersion;
		}

		public String getPageCL() {
			return this.pageCl;
		}

		public String getPageLabel() {
			return this.pageLabel;
		}

		public String getPageSTS() {
			return this.pageSts;
		}

		public String getUTCOffset() {
			return this.utcOffset;
		}

		public String getVariantsChecksum() {
			return this.variantsChecksum;
		}

		public String getXSRFToken() {
			return this.xsrfToken;
		}

		public String getAPIKey() {
			return this.apiKey;
		}

		public String getVisitorData() {
			return this.visitorData;
		}
	}

	public static int getSignatureTimestamp() {
		return MathUtil.safeParseInt(SIGNATURE_TIMESTAMP, SIGNATURE_TIMESTAMP_FALLBACK);
	}

	public static String find_value(final String page, final String key) {
		return find_value(page, key, 2);
	}

	public static String find_value_alt(final String page, final String key) {
		return find_value_alt(page, key, 2);
	}

	public static String find_value(final String page, final String key, final int chars) {
		try {
			final int b = page.indexOf(key) + key.length() + chars;
			final int e = page.indexOf('"', b);
			return page.substring(b, e);
		} catch (Exception e) {
			return null;
		}
	}

	public static String find_value_alt(final String page, final String key, final int chars) {
		try {
			final int b = page.indexOf(key) + key.length() + chars;
			final int e = page.indexOf('\'', b);
			return page.substring(b, e);
		} catch (Exception e) {
			return null;
		}
	}
}
